	package com.pwr.app.characteristics;

import java.awt.geom.Point2D;
import java.text.DecimalFormat;
import java.util.ArrayList;
import edu.emory.mathcs.jtransforms.fft.DoubleFFT_1D;

public class FourierCharacteristics implements BaseCharacteristics {

	@Override
	public double[] computeCharacteristics(boolean[][] image, int charNumber) {
		DoubleFFT_1D fft = new DoubleFFT_1D(64);
		for (int i = 0; i < image.length; i++) {
			image[i][0] = false;
			image[i][image[i].length - 1] = false;
			image[0][i] = false;
			image[image.length - 1][i] = false;
		}

		ArrayList<Point2D> sample = resample(extractCountur(image), 64);
		double[] array = Point2DtoDoubleArray(sample);
		if (array.length == 0) {
			array = new double[charNumber];
			for (int i = 0; i < array.length; i++)
				array[i] = 0;
			return array;
		}

		fft.complexForward(array);
		for (int i = 0; i < array.length; i++)
			array[i] = Math.abs(array[i]);
		double normalizeFactor = array[1];
		double[] fd6 = new double[charNumber];
		for (int i = 0; i < fd6.length; i++) {
			fd6[i] = (array[2 + i] / normalizeFactor);
		}
		return fd6;

	}

	public static final long[] TENS = new long[] { 1, 10, 100, 1000, 10000, 100000 };

	public static double round(double v, int precision) {
		long t = TENS[precision]; // contains powers of ten.
		return (double) (long) (v > 0 ? v * t + 0.5 : v * t - 0.5) / t;
	}

	public static boolean[][] filterImage(boolean[][] image) {
		int blockSize = 3;
		for (int i = 0; i < image[0].length / blockSize; i++)
			for (int l = 0; l < image.length / blockSize; l++) {
				double sum = 0;
				for (int x = 0; x < blockSize; x++)
					for (int y = 0; y < blockSize; y++) {
						if (image[l * blockSize + y][i * blockSize + x])
							sum++;
					}
				for (int x = 0; x < blockSize; x++)
					for (int y = 0; y < blockSize; y++) {
						image[l * blockSize + y][i * blockSize + x] = sum > (blockSize * blockSize) / 2;
					}
			}
		return image;
	}

	// Helper function casting Point2d to dobule[]
	public static double[] Point2DtoDoubleArray(ArrayList<Point2D> list) {
		double[] toReturn = new double[list.size() * 2];
		for (int i = 0; i < list.size(); i++) {
			toReturn[i * 2] = list.get(i).getY();
			toReturn[i * 2 + 1] = list.get(i).getX();
		}
		return toReturn;

	}

	// extracts letter countur
	public static ArrayList<Point2D> extractCountur(boolean[][] image) {
		int[][] table = { { 0, -1 }, { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 }, { 1, 0 }, { 1, -1 } };
		int offset = 4;
		int height = image[0].length;
		int width = image.length;
		int N = 8;

		ArrayList<Point2D> cords = new ArrayList<Point2D>();
		ArrayList<Integer> code = new ArrayList<Integer>();

		Point2D start = new Point2D.Double();
		Point2D current = new Point2D.Double();
		boolean isFound = false;
		for (int l = 0; l < image[0].length; l++) {
			for (int i = 0; i < image.length; i++)
				if (image[i][l]) {
					start.setLocation(l, i);
					isFound = true;
					break;
				}
			if (isFound)
				break;
		}
		current = start;
		int count = 1;
		int index = 0;
		while (true) {
			Point2D xy = new Point2D.Double(current.getX() + table[index][0], current.getY() + table[index][1]);

			if (current.getX() < 1 || current.getY() < 1 || current.getX() == width - 1 || current.getY() == height - 1) {
				break;
			}
			int k = 0;
			while (!image[(int) xy.getY()][(int) xy.getX()]) {
				k++;
				index = (index + 1) % N;
				xy = new Point2D.Double(current.getX() + table[index][0], current.getY() + table[index][1]);
				if (k > 8) {
					image[(int) current.getX()][(int) current.getY()] = false;
					for (int l = 0; l < image[0].length; l++) {
						for (int i = 0; i < image.length; i++)
							if (image[i][l]) {
								start.setLocation(l, i);
								isFound = true;
								break;
							}
						if (isFound)
							break;
					}
					current = start;
					break;
				}

			}

			cords.add(new Point2D.Double(current.getX(), current.getY()));
			code.add((index + offset) % N);
			current = new Point2D.Double(current.getX() + table[index][0], current.getY() + table[index][1]);
			index = (code.get(count - 1) + 1) % N;

			if (current.getX() == start.getX() && current.getY() == start.getY())
				break;
			count++;
		}

		if (cords.size() == 0) {

		}

		return cords;
	}

	// samples data to fixed size array
	public static ArrayList<Point2D> resample(ArrayList<Point2D> cords, int sampleSize) {
		double sum = 0;
		if (cords.size() == 0)
			return cords;
		ArrayList<Point2D> toReturn = new ArrayList<Point2D>();

		ArrayList<Point2D> cordsTemp = new ArrayList<Point2D>();
		cordsTemp.addAll(cords);
		cordsTemp.add(cordsTemp.get(0));
		cordsTemp.remove(0);
		for (int i = 0; i < cords.size(); i++) {
			sum += Math.sqrt(Math.pow(cords.get(i).getX() - cordsTemp.get(i).getX(), 2) + Math.pow(cords.get(i).getY() - cordsTemp.get(i).getY(), 2));
		}

		for (int i = 0; i < cords.size(); i++) {
			cords.get(i).setLocation(cords.get(i).getX() / sum, cords.get(i).getY() / sum);
		}

		ArrayList<Double> distances = new ArrayList<Double>();

		for (int i = 0; i < cords.size(); i++) {
			distances.add(Math.sqrt(Math.pow(cords.get(i).getX() - cordsTemp.get(i).getX(), 2) + Math.pow(cords.get(i).getY() - cordsTemp.get(i).getY(), 2)));
		}

		cordsTemp.clear();

		cords.add(cords.get(0));

		toReturn.add(cords.get(0));

		int count = 0;
		for (int i = 0; i < sampleSize; i++) {
			double remaining = (double) 1 / (double) sampleSize;
			double newX = 0;
			double newY = 0;

			while (true) {
				if (count < distances.size()) {
					if (remaining > distances.get(count)) {
						remaining = remaining - distances.get(count);
						count++;
					} else {
						double nextX = cords.get(count + 1).getX();
						double currentX = cords.get(count).getX();
						double nextY = cords.get(count + 1).getY();
						double currentY = cords.get(count).getY();
						double d = distances.get(count);

						newX = currentX + (nextX - currentX) / d * remaining;
						newY = currentY + (nextY - currentY) / d * remaining;
						distances.set(count, d - remaining);

						cords.get(count).setLocation(newX, newY);
						break;
					}
				} else
					break;
			}
			if (count >= distances.size())
				break;
			else
				toReturn.add(new Point2D.Double(newX, newY));
		}
		return toReturn;
	}
}
